<?php


namespace app\admin\library;

use think\Cache;

if (version_compare(PHP_VERSION, '5.1.2') < 0) {
    trigger_error('need php 5.1.2 or newer', E_USER_ERROR);
}

class ImSig {


//    private $key = '50be4c54304f1cd0bffd72e09c28326d8997987e41a017f1322bac2aa8c2e149';
//    private $sdkappid = 1400563320;
//    private $admin = 'administrator';
//    private $expire=  3600;
//    private $ext = '';


    private $key = '2710646b0e6b27cf6c9454daa88b480b351d71da418ebcd99c16c0c7d1f97f06';
    private $sdkappid = 1400580136;
    private $admin = 'administrator';
    private $expire=  86400;
    public $ext = 'g_';


    /**
     * 获取带前缀的user_id
     */
    public function getExtUserId($userId)
    {
        return $this->ext.$userId;
    }

    /**
     * 获取管理员的sig
     */
    public function getAdimnSig(){
        $sig = Cache::get('admin_im_'.$this->admin);
        $sig = 0;
        if(!$sig){
            $sig = $this->genSig($this->admin, $this->expire,false);
            Cache::set('admin_im_'.$this->admin,$sig, $this->expire - 300);
        }
        return $sig;
    }

    /**
     * 检查一个账号是否存在
     */
    public function account_check_exist($str='',$ext=true){
        $res = $this->account_check($str,$ext);
        if($ext)
            $str = $this->ext.$str;
        if(!isset($res[$str])) throw new \Exception('账号查询失败');
        if($res[$str]['ResultCode'] == 0 &&  $res[$str]['AccountStatus'] == 'NotImported'){
            return false;
        }elseif ($res[$str]['ResultCode'] == 0 &&  $res[$str]['AccountStatus'] == 'Imported'){
            return true;
        }else{
            throw new \Exception('账号查询错误');
        }
    }

    /**
     * 查询帐号
     */
    public function account_check($data,$ext=true){
        $params = [];
        if(!$data) throw new \Exception('UserID 参数不存在');
        if(is_array($data)){
            foreach ($data as $value){
                if($ext) $value = $this->ext.$value;
                $params['CheckItem'][] = [
                    'UserID'=>(string)$value
                ];
            }
        }else{
            if($ext) $data = $this->ext.$data;
            $params['CheckItem'][] = [
                'UserID'=>(string)$data
            ];
        }
        $url = 'https://console.tim.qq.com/v4/im_open_login_svc/account_check';
        $url = $this->getUrl($url);
        $data = $this->curl_post($url,$params);
        $new_data = [];
        foreach ( $data as  $value){
            $new_data[$value['UserID']] = $value;
        }
        return $new_data;
    }

    /**
     * 创建账号
     */
    public function account_import($data,$ext=true){
        $url = 'https://console.tim.qq.com/v4/im_open_login_svc/account_import';
        $url = $this->getUrl($url);
        if(!isset($data['Identifier']) && $data['Identifier']) throw new \Exception('Identifier 参数必填');
        if($ext) $data['Identifier'] = $this->ext.$data['Identifier'];
        $data = $this->curl_post($url,$data);
        return $data;
    }


    /**
     * 查询账号是否在线
     */
    public function queryStates($user,$ext=true)
    {
        $url = 'https://console.tim.qq.com/v4/openim/querystate';
        $url = $this->getUrl($url);
        if(!$user) throw new \Exception('Identifier 参数必填');
        if(is_array($user)){
            $data = [];
            foreach ($user as $value){
                if($ext) $value = $this->ext.$value;
                $data['To_Account'][] = $value;
            }
        }else{
            if($ext) $user = $this->ext.$user;
            $data = [
                'To_Account'=>[$user]
            ];
        }
        return $this->curl_post($url,$data);
    }


    /**
     * 封装管理url
     */
    public function getUrl($url)
    {
        if(!$url) throw new \Exception('请求地址不存在');
        $url .= '?sdkappid='.$this->sdkappid.'&identifier='.$this->admin.'&usersig='.$this->getAdimnSig().'&random='.rand(1,99999999).'&contenttype=json';
        return $url;
    }


    /**
     * 用于 url 的 base64 encode
     * '+' => '*', '/' => '-', '=' => '_'
     * @param string $string 需要编码的数据
     * @return string 编码后的base64串，失败返回false
     * @throws \Exception
     */
    private function base64_url_encode($string) {
        static $replace = Array('+' => '*', '/' => '-', '=' => '_');
        $base64 = base64_encode($string);
        if ($base64 === false) {
            throw new \Exception('base64_encode error');
        }
        return str_replace(array_keys($replace), array_values($replace), $base64);
    }

    /**
     * 用于 url 的 base64 decode
     * '+' => '*', '/' => '-', '=' => '_'
     * @param string $base64 需要解码的base64串
     * @return string 解码后的数据，失败返回false
     * @throws \Exception
     */
    private function base64_url_decode($base64) {
        static $replace = Array('+' => '*', '/' => '-', '=' => '_');
        $string = str_replace(array_values($replace), array_keys($replace), $base64);
        $result = base64_decode($string);
        if ($result == false) {
            throw new \Exception('base64_url_decode error');
        }
        return $result;
    }

    /**
     * 使用 hmac sha256 生成 sig 字段内容，经过 base64 编码
     * @param $identifier 用户名，utf-8 编码
     * @param $curr_time 当前生成 sig 的 unix 时间戳
     * @param $expire 有效期，单位秒
     * @param $base64_userbuf base64 编码后的 userbuf
     * @param $userbuf_enabled 是否开启 userbuf
     * @return string base64 后的 sig
     */
    private function hmacsha256($identifier, $curr_time, $expire, $base64_userbuf, $userbuf_enabled) {
        $content_to_be_signed = "TLS.identifier:" . $identifier . "\n"
            . "TLS.sdkappid:" . $this->sdkappid . "\n"
            . "TLS.time:" . $curr_time . "\n"
            . "TLS.expire:" . $expire . "\n";
        if (true == $userbuf_enabled) {
            $content_to_be_signed .= "TLS.userbuf:" . $base64_userbuf . "\n";
        }
        return base64_encode(hash_hmac( 'sha256', $content_to_be_signed, $this->key, true));
    }

    /**
     * 生成签名。
     *
     * @param $identifier 用户账号
     * @param int $expire 过期时间，单位秒，默认 180 天
     * @param $userbuf base64 编码后的 userbuf
     * @param $userbuf_enabled 是否开启 userbuf
     * @return string 签名字符串
     * @throws \Exception
     */
    private function __genSig($identifier, $expire, $userbuf, $userbuf_enabled) {
        $curr_time = time();
        $sig_array = Array(
            'TLS.ver' => '2.0',
            'TLS.identifier' => strval($identifier),
            'TLS.sdkappid' => intval($this->sdkappid),
            'TLS.expire' => intval($expire),
            'TLS.time' => intval($curr_time)
        );

        $base64_userbuf = '';
        if (true == $userbuf_enabled) {
            $base64_userbuf = base64_encode($userbuf);
            $sig_array['TLS.userbuf'] = strval($base64_userbuf);
        }

        $sig_array['TLS.sig'] = $this->hmacsha256($identifier, $curr_time, $expire, $base64_userbuf, $userbuf_enabled);
        if ($sig_array['TLS.sig'] === false) {
            throw new \Exception('base64_encode error');
        }
        $json_str_sig = json_encode($sig_array);
        if ($json_str_sig === false) {
            throw new \Exception('json_encode error');
        }
        $compressed = gzcompress($json_str_sig);
        if ($compressed === false) {
            throw new \Exception('gzcompress error');
        }
        return $this->base64_url_encode($compressed);
    }


    /**
     * 生成签名
     *
     * @param $identifier 用户账号
     * @param int $expire 过期时间，单位秒，默认 180 天
     * @return string 签名字符串
     * @throws \Exception
     */
    public function genSig($identifier, $expire=0,$ext = true) {
        if($ext) $identifier = $this->ext.$identifier;
        if(!$expire) $expire = $this->expire;
        return $this->__genSig($identifier, $expire, '', false);
    }

    /**
     * 带 userbuf 生成签名。
     * @param $identifier 用户账号
     * @param int $expire 过期时间，单位秒，默认 180 天
     * @param string $userbuf 用户数据
     * @return string 签名字符串
     * @throws \Exception
     */
    public function genSigWithUserBuf($identifier, $expire, $userbuf,$ext=true) {
        if($ext) $identifier = $this->ext.$identifier;
        return $this->__genSig($identifier, $expire, $userbuf, true);
    }


    /**
     * 验证签名。
     *
     * @param string $sig 签名内容
     * @param string $identifier 需要验证用户名，utf-8 编码
     * @param int $init_time 返回的生成时间，unix 时间戳
     * @param int $expire_time 返回的有效期，单位秒
     * @param string $userbuf 返回的用户数据
     * @param string $error_msg 失败时的错误信息
     * @return boolean 验证是否成功
     * @throws \Exception
     */
    private function __verifySig($sig, $identifier, &$init_time, &$expire_time, &$userbuf, &$error_msg) {
        try {
            $error_msg = '';
            $compressed_sig = $this->base64_url_decode($sig);
            $pre_level = error_reporting(E_ERROR);
            $uncompressed_sig = gzuncompress($compressed_sig);
            error_reporting($pre_level);
            if ($uncompressed_sig === false) {
                throw new \Exception('gzuncompress error');
            }
            $sig_doc = json_decode($uncompressed_sig);
            if ($sig_doc == false) {
                throw new \Exception('json_decode error');
            }
            $sig_doc = (array)$sig_doc;
            if ($sig_doc['TLS.identifier'] !== $identifier) {
                throw new \Exception("identifier dosen't match");
            }
            if ($sig_doc['TLS.sdkappid'] != $this->sdkappid) {
                throw new \Exception("sdkappid dosen't match");
            }
            $sig = $sig_doc['TLS.sig'];
            if ($sig == false) {
                throw new \Exception('sig field is missing');
            }

            $init_time = $sig_doc['TLS.time'];
            $expire_time = $sig_doc['TLS.expire'];

            $curr_time = time();
            if ($curr_time > $init_time+$expire_time) {
                throw new \Exception('sig expired');
            }

            $userbuf_enabled = false;
            $base64_userbuf = '';
            if (isset($sig_doc['TLS.userbuf'])) {
                $base64_userbuf = $sig_doc['TLS.userbuf'];
                $userbuf = base64_decode($base64_userbuf);
                $userbuf_enabled = true;
            }
            $sigCalculated = $this->hmacsha256($identifier, $init_time, $expire_time, $base64_userbuf, $userbuf_enabled);

            if ($sig != $sigCalculated) {
                throw new \Exception('verify failed');
            }

            return true;
        } catch (\Exception $ex) {
            $error_msg = $ex->getMessage();
            return false;
        }
    }


    /**
     * 带 userbuf 验证签名。
     *
     * @param string $sig 签名内容
     * @param string $identifier 需要验证用户名，utf-8 编码
     * @param int $init_time 返回的生成时间，unix 时间戳
     * @param int $expire_time 返回的有效期，单位秒
     * @param string $error_msg 失败时的错误信息
     * @return boolean 验证是否成功
     * @throws \Exception
     */
    public function verifySig($sig, $identifier, &$init_time, &$expire_time, &$error_msg) {
        $userbuf = '';
        return $this->__verifySig($sig, $identifier, $init_time, $expire_time, $userbuf, $error_msg);
    }

    /**
     * 验证签名
     * @param string $sig 签名内容
     * @param string $identifier 需要验证用户名，utf-8 编码
     * @param int $init_time 返回的生成时间，unix 时间戳
     * @param int $expire_time 返回的有效期，单位秒
     * @param string $userbuf 返回的用户数据
     * @param string $error_msg 失败时的错误信息
     * @return boolean 验证是否成功
     * @throws \Exception
     */
    public function verifySigWithUserBuf($sig, $identifier, &$init_time, &$expire_time, &$userbuf, &$error_msg) {
        return $this->__verifySig($sig, $identifier, $init_time, $expire_time, $userbuf, $error_msg);
    }


    /**
     * @param string $url post请求地址
     * @param array $params
     * @return mixed
     */
    private function curl_post($url, array $params = array())
    {
        $data_string = json_encode($params);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
        curl_setopt(
            $ch, CURLOPT_HTTPHEADER,
            array(
                'Content-Type: application/json'
            )
        );
        $data = curl_exec($ch);
        curl_close($ch);
        $data = json_decode($data,true);
        if(isset($data['ActionStatus']) && $data['ActionStatus'] == 'OK' && $data['ErrorCode'] == 0 ){
            if(isset($data['ResultItem'])){
                return $data['ResultItem'];
            }
            return $data;
        }else{
            if(isset($data['ErrorCode'])) throw new \Exception($data['ErrorCode'].'---'.$data['ErrorInfo']);
            throw new \Exception('微信请求未知错误');
        }
    }
}